package com.gitee.huanminabc.multimedia.ffmpeg;

import com.gitee.huanminabc.jcommon.base.DateUtil;
import com.gitee.huanminabc.jcommon.enums.DatePatternEnum;
import com.gitee.huanminabc.jcommon.file.FilePathPartEnum;
import com.gitee.huanminabc.jcommon.file.FileUtil;
import com.gitee.huanminabc.jcommon.file.FileWriterUtil;
import com.gitee.huanminabc.utils_common.enums.DateEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

import java.io.File;
import java.util.*;

/**
 * width 和 height 可以指定为-1,表示按照原视频的宽高比例进行操作,只能指定一个,另一个自动计算
 */
@Slf4j
public class FfmpegVideoUtil {

    /**
     * 获取ffmpeg默认路径
     */
    public static String GetDefaultFFMPEGPath() {
        return new File(System.getProperty("java.io.tmpdir"), "jave/").getAbsolutePath();
    }

    /**
     * 将第多少秒转换为视频时间(HH:mm:ss)
     * 例: 第70秒为00:01:10
     *
     * @param second 视频中的第多少秒
     */
    public static String transferVideoTime(int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.add(Calendar.SECOND, second);
        //转换为时间
        Date time = calendar.getTime();
        return DateUtil.format(time, DatePatternEnum.TIME_PATTERN);
    }


    /**
     * 添加水印
     *
     * @param inputPath     输入视频路径
     * @param waterMarkPath 水印文件路径
     * @param x             距离左上角水平距离
     * @param y             距离左上角垂直距离
     * @param outputPath    输出视频路径
     */
    public static boolean AddWatermark(String inputPath, String waterMarkPath,
                                       int x, int y, String outputPath) {
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(waterMarkPath);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument("-filter_complex");
            ffmpeg.addArgument("overlay=" + x + ":" + y);
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("视频添加水印失败,文件:{},error:{}", inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("视频添加水印异常", e);
            return false;
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
    }

    //通过计算图片的宽高比例，来确定图片在右下角的位置
    public static boolean AddWatermark(String inputPath, String waterMarkPath, String outputPath) {
        //获取图片的宽和高
        int[] ints = FfmpegInfoUtil.GeWidthAndHeight(waterMarkPath);
        int width = ints[0];
        int height = ints[1];
        //获取视频的宽和高
        int[] ints1 = FfmpegInfoUtil.GeWidthAndHeight(inputPath);
        int videoWidth = ints1[0];
        int videoHeight = ints1[1];
        //计算图片在右下角的位置
        int x = videoWidth - width - 100;
        int y = videoHeight - height - 100;
        return AddWatermark(inputPath, waterMarkPath, x, y, outputPath);
    }


    /**
     * 生成视频缩略图
     *
     * @param inputPath  输入视频
     * @param outputPath 输出图片
     * @param second     第几秒的缩略图
     */
    public static boolean generateThumbnail(String inputPath, String outputPath, int second) {
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-ss");
            ffmpeg.addArgument(transferVideoTime(second));
            ffmpeg.addArgument("-vframes");
            ffmpeg.addArgument("1");
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("生成视频缩略图失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("生成视频缩略图异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 修改视频分辨率 ,修改分辨率超过原视频,不能提高画质, 只是用于在不同分辨率的设备上播放,高分辨率转低分辨率会失真,低分辨率转高分辨率会模糊,不建议使用除非必要
     * 标清SD（Standard Definition） 480p 640x480 720x480
     * 高清 HD（High Definition） 720p 960x720 1280x720   1080p 1440x1080 1920x1080
     * 超高清UHD（Ultra High Definition） 4k 4096×3112 4096*2160
     *
     * @param inputPath  视频来源地址
     * @param outputPath 输出视频地址
     * @param width      宽度
     * @param height     高度
     */
    public static boolean changeScale(String inputPath, String outputPath, int width, int height) {
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("scale=" + width + ":" + height);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频分辨率失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频分辨率异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频码率 ,quality值越大,视频质量越高,码率越大,视频文件越大,quality值越小,视频质量越低,码率越小,视频文件越小 ,这个值是个大概值,具体效果需要自己测试
    //一般来说1080p视频码率在5000k-8000k之间,720p视频码率在3000k-5000k之间,480p视频码率在1000k-3000k之间
    //对应的quality值分别为5000,3000,1000
    public static boolean changeQuality(String inputPath, String outputPath, int quality) {
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-b:v");
            ffmpeg.addArgument(quality + "k");
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频码率失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频码率异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //编码格式转换 codec: h264, h265, mpeg4, vp9, vp8,等
    //codec : h264是目前最常用的编码格式,但是h265压缩率更高,画质更好,文件更小,但是h265编码速度慢,解码速度慢,不支持硬件加速,不支持的设备无法播放
    //mepg4是老的编码格式,画质一般,文件大,不推荐使用
    //vp9, vp8是google开发的编码格式,画质一般,文件小,但是编码速度慢,解码速度慢,不支持的设备无法播放
    // 一般来说h264是最好的选择,如果对画质要求不高,可以选择h265,如果对文件大小要求很高,可以选择vp9, vp8
    //h265=libx265  h264=libx264  mpeg4=libxvid  vp9=libvpx-vp9  vp8=libvpx
    //如果看到视频的编码格式为: HEVC（High Efficiency Video Coding），也被称为H.265
    //如果看到视频的编码格式为: AVC（Advanced Video Coding），也被称为H.264, 现在很多直接写成h264 (High) (avc1 / 0x31637661)
    //如果看到视频的编码格式为: VP9，是Google开发的开源视频编码格式
    public static boolean changeCodec(String inputPath, String outputPath, String codec) {
        //codec做映射 ,比如输入h264 转换为 libx264
        if ("h264".equalsIgnoreCase(codec)) {
            codec = "libx264";
        } else if ("h265".equalsIgnoreCase(codec)) {
            codec = "libx265";
        } else if ("mpeg4".equalsIgnoreCase(codec)) {
            codec = "libxvid";
        } else if ("vp9".equalsIgnoreCase(codec)) {
            codec = "libvpx-vp9";
        } else if ("vp8".equalsIgnoreCase(codec)) {
            codec = "libvpx";
        } else {
            throw new RuntimeException("codec参数只能是h264,h265,mpeg4,vp9,vp8");
        }

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-c:v");
            ffmpeg.addArgument(codec);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频编码格式失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频编码格式异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频帧率,一般来说视频帧率是24,25,30,60,帧率越高,视频越流畅,但是文件越大,帧率越低,视频越卡,但是文件越小
    //一般来说24帧是电影,25帧是电视,30帧是电视,60帧是游戏,如果是电影,可以选择24帧,如果是电视,可以选择25帧,如果是游戏,可以选择60帧
    //frameRate最大值为60,最小值为1,一般来说24,25,30,60就可以了
    public static boolean changeFrameRate(String inputPath, String outputPath, int frameRate) {
        //限制帧率
        if (frameRate < 1 || frameRate > 60) {
            throw new RuntimeException("frameRate参数只能在1~60之间");
        }
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-r");
            ffmpeg.addArgument(java.lang.String.valueOf(frameRate));
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频帧率失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频帧率异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频亮度和变暗,亮度值越大,视频越亮,亮度值越小,视频越暗,亮度值范围为-1~1之间,0是不提亮,1是完全提亮,0.5是提亮一半 ,如果视频太暗,可以提高亮度可以试试0.02这样一点点提亮
    public static boolean changeBrightness(String inputPath, String outputPath, double brightness) {
        //限制亮度
        if (brightness < -1 || brightness > 1) {
            throw new RuntimeException("brightness参数只能在-1~1之间");
        }

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("eq=brightness=" + brightness);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频亮度失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频亮度异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频对比度,对比度值越大,视频对比度越高,对比度值越小,视频对比度越低,contrast是对比度, 1~3之间,1是不调整,3是最大对比度
    //对比度的意思就是,黑白之间的差异,对比度越大,黑白之间的差异越大,对比度越小,黑白之间的差异越小, 简单来说就是黑的更黑,白的更白
    public static boolean changeContrast(String inputPath, String outputPath, double contrast) {
        //限制对比度
        if (contrast < 1 || contrast > 3) {
            throw new RuntimeException("contrast参数只能在1~3之间,虽然支持-1000~1000,但是一般不会这么大会导致图片直接变成黑白");
        }

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("eq=contrast=" + contrast);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频对比度失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频对比度异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频饱和度,饱和度值越大,视频饱和度越高,饱和度值越小,视频饱和度越低,saturation是饱和度,0~3之间,0是完全不饱和,3是完全饱和
    public static boolean changeSaturation(String inputPath, String outputPath, double saturation) {
        //限制饱和度
        if (saturation < 0 || saturation > 3) {
            throw new RuntimeException("saturation参数只能在0~3之间");
        }

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("eq=saturation=" + saturation);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频饱和度失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频饱和度异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //视频锐化, radius是锐化半径,1~5之间,越大锐化效果越明显,但是也会导致图片失真
    public static void sharpenVide(String inputPath, String outputPath, double radius) {
        //radius只能在1~5之间
        if (radius < 1 || radius > 5) {
            throw new RuntimeException("radius参数只能在1~5之间");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-vf");
        cmds.add("unsharp=5:5:" + radius + ":5:5:0.0");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //调整视频音量,音量值越大,音量越大,音量值越小,音量越小,volume是音量 ,0没有声音了,0.5是一半音量,1是原音量,2是两倍音量 ,3是三倍音量, 如果提升的太大,会有噪音或者改变声音原本的音 ,这里限制最低0
    public static boolean changeVolume(String inputPath, String outputPath, double volume) {
        if (volume < 0) {
            throw new RuntimeException("volume参数必须大于等于0");
        }
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-af");
            ffmpeg.addArgument("volume=" + volume);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频音量失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频音量异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //调整视频速度,速度值越大,视频播放速度越快,速度值越小,视频播放速度越慢, speed是速度,0.5是一半速度,1是原速度,2是两倍速度,3是三倍速度,如果提升的太大,会有卡顿或者丢帧,这里限制最低0
    public static boolean changeSpeed(String inputPath, String outputPath, double speed) {
        if (speed < 0) {
            throw new RuntimeException("speed参数必须大于等于0");
        }
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-filter_complex");
            ffmpeg.addArgument("[0:v]setpts=" + 1 / speed + "*PTS[v];[0:a]atempo=" + speed + "[a]");
            ffmpeg.addArgument("-map");
            ffmpeg.addArgument("[v]");
            ffmpeg.addArgument("-map");
            ffmpeg.addArgument("[a]");
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("调整视频速度失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频速度异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //  裁剪视频
    //input.mp4 是输入视频文件。
    //output.mp4 是裁剪后的输出视频文件。
    //width 和 height 分别指定裁剪区域的宽度和高度。
    //x 和 y 分别指定裁剪区域左上角的x和y坐标。
    //例如，如果你想要从原始视频的左上角裁剪出一个宽度为500像素、高度为300像素的视频  500:300:0:0
    //例如: 如果你想从视频的中心裁剪出一个区域   512:512:(iw-512)/2:(ih-512)/2   其中iw和ih分别是输入视频的宽度和高度。这样，裁剪区域就会在原始视频的中心。
    public static boolean cutVideo(String inputPath, String outputPath, int x, int y, int width, int height) {
        //判断裁剪区域是否合法,不能超过原视频的宽高, 同时还需要计算坐标的影响
        int[] ints = FfmpegInfoUtil.GeWidthAndHeight(inputPath);
        int width1 = ints[0];
        int height1 = ints[1];
        if (x < 0 || y < 0 || width < 0 || height < 0 || x + width > width1 || y + height > height1) {
            throw new RuntimeException("裁剪区域不合法,不能超过原视频的宽高, 同时还需要计算坐标的影响");
        }

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("crop=" + width + ":" + height + ":" + x + ":" + y);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("裁剪视频失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("裁剪视频异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }


    /**
     * 复制视频片段
     *
     * @param inputPath  视频来源地址
     * @param outputPath 输出视频地址
     * @param startTime  开始时间,01:02:03在视频的第1小时第2分钟第3秒处
     * @param endTime    结束时间,格式同startTime
     */
    public static boolean cutVideo(String inputPath, String outputPath, int startTime, int endTime) {
        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument("-ss");
            ffmpeg.addArgument(transferVideoTime(startTime));
            ffmpeg.addArgument("-to");
            ffmpeg.addArgument(transferVideoTime(endTime));


            ffmpeg.addArgument("-c:v");
            //获取视频编码格式,然后复制,不然会导致视频前几秒黑屏或者定格
            String videoCodecToFFmpeg = FfmpegInfoUtil.getVideoCodecToFFmpeg(inputPath);
            ffmpeg.addArgument(videoCodecToFFmpeg);


            ffmpeg.addArgument("-c:a");
            ffmpeg.addArgument("copy");

            ffmpeg.addArgument(outputPath);
            //执行
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("复制视频失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("复制视频片段异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }


    /**
     * 格式转换
     *
     * @param inputPath  视频来源地址 例如: F:\java-project\java-ffmpeg\src\test\resources\java\20240323_163756.mp4
     * @param outputPath 输出视频地址 例如: F:\java-project\java-ffmpeg\src\test\resources\java\back\20240323_163756.avi
     */
    public static boolean transferFormat(String inputPath, String outputPath) {

        ProcessWrapper ffmpeg = null;
        try {
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-y");
            ffmpeg.addArgument("-c");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument(outputPath);
            //执行
            ffmpeg.execute();

            //等待完成
            String errorMsg = FfmpegInfoUtil.WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("转换视频格式失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("转换视频格式异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    //将视频旋转, 传入一个参数来控制选择的程度
    //图片旋转,1是顺时针旋转90度,2是逆时针旋转90度,3是顺时针旋转180度
    public static void rotateVide(String inputPath, String outputPath, int rotate) {
        //判断输入
        if (rotate < 1 || rotate > 3) {
            throw new RuntimeException("rotate参数只能是1,2,3");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-vf");
        cmds.add("transpose=" + rotate);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //视频翻转,0是水平翻转,1是垂直翻转
    public static void flipVide(String inputPath, String outputPath, int flip) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-vf");
        if (flip == 0) {//水平翻转
            cmds.add("hflip");
        } else if (flip == 1) { //垂直翻转
            cmds.add("vflip");
        } else {
            throw new RuntimeException("flip参数只能是0,1");
        }
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }


    //视频模糊, radius是模糊半径,1~5之间,越大模糊效果越明显,但是也会导致图片失真
    public static void blurVide(String inputPath, String outputPath, double radius) {
        //radius只能在1~5之间
        if (radius < 1 || radius > 5) {
            throw new RuntimeException("radius参数只能在1~5之间");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-vf");
        cmds.add("gblur=sigma=" + radius);
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    // 视频压缩  quality值越大压缩越高,画质越差,quality值越小,压缩越低,画质越好,这个值是个大概值,具体效果需要自己测试  ,29差不多是原画质, 最高到39不会太改变画质,35以上画质就会变差得很明显
    //同时默认会将非h265编码的视频转换为h265编码,这样编码就会更小,画质更好 ,如果本身就是h265编码,那么视频本身就是压缩过的,压缩的效果就会稍微差一些
    public static void compressVide(String inputPath, String outputPath, int quality) {
        //限制quality值29~35之间  ,35以上画质就会变差得很明显
        if (quality < 29 || quality > 39) {
            throw new RuntimeException("quality参数只能在29~39之间,29差不多是原画质,最高到39不会太改变画质");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        //判断视频如果不是h265编码,就转换为h265编码 ,这样编码就会更小,画质更好
        String videoCodec = FfmpegInfoUtil.getVideoCodec(inputPath);
        if (!"h265".equals(videoCodec)) {
            cmds.add("-c:v");
            cmds.add("libx265");
        }
        cmds.add("-crf");
        cmds.add(java.lang.String.valueOf(quality));
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);

    }


    //将视频进行直播推流,推流到rtmp服务器,可以用于直播
    //rtmp服务器可以自己搭建,也可以使用第三方的,比如腾讯云,阿里云,网易云等
    //推流地址可以自己设置,比如 rtmp://
    public static void pushRtmpStream(String inputPath, String pushUrl) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-re");
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-f");
        cmds.add("flv");
        cmds.add(pushUrl);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //rtsp流推送
    public static void pushRtspStream(String inputPath, String pushUrl) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-re");
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-f");
        cmds.add("rtsp");
        cmds.add(pushUrl);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }

    //拉流
    public static void pullStream(String pullUrl, String outputPath) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(pullUrl);
        cmds.add("-y");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }


    //查询摄像头名称
    //https://github.com/rdp/screen-capture-recorder-to-video-windows-free/releases 在windwos中需要安装这个软件,然后才能查询摄像头名称
    public static String  queryCameraName() {
        List<String> cmds = new ArrayList<>();
        cmds.add("-list_devices");
        cmds.add("true");
        cmds.add("-f");
        cmds.add("dshow");
        cmds.add("-i");
        cmds.add("dummy");
        List<String> list = FfmpegInfoUtil.ExecuteCmdReturn(cmds);
        //遍历结果找到DirectShow video devices ,然后他的下一个结果就是摄像头名称
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).contains("DirectShow video devices")) {
                //[dshow @ 000001ff4280b2c0]  "screen-capture-recorder" 需要截取这个摄像头名称
                String str = list.get(i + 1);
                String[] split = str.split("\"");
                if (split.length > 1) {
                    return split[1];
                }
            }
        }
        return null;
    }


    //windwos 摄像头推流
    //cameraName 摄像头名称,可以通过ffmpeg -list_devices true -f dshow -i dummy命令查看
    public static void pushWindowsCamera(String cameraName, String pushUrl) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-f");
        cmds.add("dshow");
        cmds.add("-i");
        cmds.add("video=" + cameraName);
        cmds.add("-f");
        cmds.add("flv");
        cmds.add(pushUrl);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }


    //将视频转换为gif ,可以利用上面的copyVideo功能取出想要的片段,然后转换为gif
    public static void videoToGif(String inputPath, String outputPath, int fps, int width, int height) {
        //判断输出后缀是否是gif
        if (!outputPath.endsWith(".gif")) {
            throw new RuntimeException("输出文件必须是gif格式");
        }
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-y");
        cmds.add("-vf");
        cmds.add("fps=" + fps + ",scale=" + width + ":" + height + ":flags=lanczos");
        cmds.add("-c:v");
        cmds.add("gif");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);
    }




    //修改视频的SAR
    //sar: 是视频的宽高比,比如16:9,4:3,1:1等,如果视频的宽高比不对,会导致视频变形,这个时候就需要调整SAR
    public static void changeSAR(String inputPath, String outputPath, String sar) {
        List<String> cmds = new ArrayList<>();
        cmds.add("-i");
        cmds.add(inputPath);
        cmds.add("-vf");
        cmds.add("setsar=" + sar);
        cmds.add("-c:v");
        cmds.add("libx264");
        cmds.add("-c:a");
        cmds.add("copy");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);

    }


    //合成多个视频  使用ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4方式  ,这种方式会自动识别然后进行合并 ,而且这种方式也是最快的
    public static void mergeVideo(String outputPath, String... inputPaths) {
        //获取输出视频目录
        String currentDirectoryOut = FileUtil.getFilePathPartInfo(inputPaths[0], FilePathPartEnum.currentDirectory);
        //创建list.txt文件
        String listPath = currentDirectoryOut + File.separator + UUID.randomUUID() + "mergeVideo-list.txt";
        StringBuilder sb = new StringBuilder();
        for (String inputPath : inputPaths) {
            sb.append("file '").append(inputPath).append("'\n");
        }
        FileWriterUtil.writerStrToFile( sb.toString(),new File(listPath));

        List<String> cmds = new ArrayList<>();
        cmds.add("-f");
        cmds.add("concat");
        cmds.add("-safe");
        cmds.add("0");
        cmds.add("-i");
        cmds.add(listPath);
        cmds.add("-y");
        cmds.add("-c");
        cmds.add("copy");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);

        //删除临时文件
        new File(listPath).delete();


    }



    //将图片转换为视频,可以认为是关键帧,然后通过关键帧合成视频
    //fps是帧率 ,duration是视频时长,单位秒, width和height是视频分辨率
    //生成的关键帧视频是必须有声音的,否则后期合成视频会出现问题导致视频卡死或者无法播放的问题
    //如果想要静音可以自己做一个静音的音频文件,然后合成视频的时候加上静音音频这样就可以了。
    public static void imageToVideo(String imagePath,String audioPath, String outputPath, int duration, int fps, int width, int height) {
        List<String> cmd = new ArrayList<>();
        cmd.add("-y");
        cmd.add("-loop");
        cmd.add("1");
        cmd.add("-i");
        cmd.add(imagePath);
        cmd.add("-i");
        cmd.add(audioPath); // 添加音频文件作为输入
        cmd.add("-t");
        cmd.add(java.lang.String.valueOf(duration)); // 设置输出视频的时长（可选，如果希望视频与音频长度一致则不需要）
        cmd.add("-vf");
        cmd.add("fps=" + fps + ",scale=" + width + ":" + height); // 设置帧率和视频尺寸
        cmd.add("-c:v");
        cmd.add("libx264"); // 设置视频编解码器
        cmd.add("-c:a");
        cmd.add("aac"); // 设置音频编解码器为AAC（或根据需要选择其他编解码器）
        cmd.add("-pix_fmt");
        cmd.add("yuv420p"); // 设置像素格式
        cmd.add("-shortest"); // 如果图像应该循环以匹配音频长度，则包含此选项；否则，如果音频比图像短，视频将在音频结束时停止
        cmd.add(outputPath);

        FfmpegInfoUtil.ExecuteCmd(cmd);
    }


    //将视频插入到另一个视频中的某个时间点
    //1.先切割视频,将视频分为两段,第一段是插入视频之前的视频,第二段是插入视频之后的视频
    //2.将第一段视频和插入视频合成为一个视频
    //3.将第二段视频和合成的视频合成为一个视频
    // 请注意，这种合并方法适用于所有视频具有相同编解码参数的情况。
    // 如果视频具有不同的编解码参数、分辨率或帧率，你可能需要先对它们进行转换，使其参数一致，再进行合并。
    public static void insertVideo(String inputPath, String insertPath, String outputPath, int insertTime) {
        //判断插入时间是否合法
        if (insertTime < 0) {
            throw new RuntimeException("insertTime参数必须大于等于0");
        }
        //判断插入视频是否存在
        if (!new File(insertPath).exists()) {
            throw new RuntimeException("插入视频不存在");
        }
        //判断插入视频的时长是否大于插入时间
        int insertVideoTime = FfmpegInfoUtil.getTime(insertPath);
        if (insertTime > insertVideoTime) {
            throw new RuntimeException("插入时间不能大于插入视频的时长");
        }
        //判断插入时间是否大于原视频的时长
        int videoTime = FfmpegInfoUtil.getTime(inputPath);
        if (insertTime > videoTime) {
            throw new RuntimeException("插入时间不能大于原视频的时长");
        }

        //获取输出视频目录

        //切割视频,将视频分为两段,第一段是插入视频之前的视频,第二段是插入视频之后的视频
        String firstVideo = FfmpegInfoUtil.getOutputCachePath(outputPath, "insertVideo-first.mp4");
        String endVideo = FfmpegInfoUtil.getOutputCachePath(outputPath, "insertVideo-end.mp4");
        cutVideo(inputPath, firstVideo, 0, insertTime);
        cutVideo(inputPath, endVideo, insertTime, videoTime);

        //将3个视频合成为一个视频  ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4    内容格式file 'original_part1.mp4'
        String listPath = FfmpegInfoUtil.getOutputCachePath(outputPath, "insertVideo-list.txt");
        FileWriterUtil.writerStrToFile("file '" + firstVideo + "'\nfile '" + insertPath + "'\nfile '" + endVideo + "'\n",new File(listPath));
        List<String> cmds = new ArrayList<>();
        cmds.add("-f");
        cmds.add("concat");
        cmds.add("-safe");
        cmds.add("0");
        cmds.add("-i");
        cmds.add(listPath);
        cmds.add("-y");
        cmds.add("-c");
        cmds.add("copy");
        cmds.add(outputPath);
        FfmpegInfoUtil.ExecuteCmd(cmds);

        //删除临时文件
        new File(firstVideo).delete();
        new File(endVideo).delete();
        new File(listPath).delete();



    }





}
